var ENT_amp = '&amp;';
var ENT_lt = '&lt;';
var ENT_gt = '&gt;';
var ENT_quot = '&quot;';
var ENT_apos = '&apos;';
var ENT_aposnumeric = '&#39;';
var REENT_amp = /&amp;/g;
var REENT_lt = /&lt;/g;
var REENT_gt = /&gt;/g;
var REENT_quot = /&quot;/g;
var REENT_apos = /&apos;/g;
var REENT_aposnumeric = /&#39;/g;
var CHAR_amp = '&';
var CHAR_lt = '<';
var CHAR_gt = '>';
var CHAR_quot = '"';
var CHAR_apos = "'";
/**
 * Property names used in JSON response from feature requests.
 */
var ONION_REFERENCE_ZOOM = 23;
var ONION_PROP_ANCHOR = 'a';
var ONION_PROP_BASE = 'base';
var ONION_PROP_BOUNDING_BOX = 'bb';
var ONION_PROP_DESCRIPTION = 'dd';
var ONION_PROP_DISTANCE = 'd';
var ONION_PROP_FEATURES = 'features';
var ONION_PROP_IW_OFFSET = 'io';
var ONION_PROP_LAYER = 'layer';
var ONION_PROP_MARKER_BASE = 'mb';
var ONION_PROP_RASTER = 'raster';
var ONION_PROP_ZRANGE = 'zrange';
var ONION_PROP_CONTENT = 'c';
var ONION_PROP_BASIC_CONTENT = '1';
var ONION_PROP_TITLE = 'title';
var ONION_PROP_CID = 'id';

/**
 * Unescapes XML markup characters that must not occur in XML text.
 *
 * @param {string} str Input xml.
 * @return {string} Unescaped xml.
 */
function xmlUnescapeText(str) {
    if (str.indexOf(ENT_lt) != -1) {
	str = str.replace(REENT_lt, CHAR_lt);
    }

    if (str.indexOf(ENT_gt) != -1) {
	str = str.replace(REENT_gt, CHAR_gt);
    }

    if (str.indexOf(ENT_amp) != -1) {
	str = str.replace(REENT_amp, CHAR_amp);
    }

    return str;
}

/**
 * Unescapes XML markup characters that must not occur in XML attribute
 * values.  This also happens to handle the set of characters encoded by
 * the C++ function HtmlUtils::EscapeString().
 *
 * @param {string} str Input xml.
 * @return {string} Unescaped xml.
 */
function xmlUnescapeAttr(str) {
    if (str.indexOf(ENT_quot) != -1) {
	str = str.replace(REENT_quot, CHAR_quot);
    }

    // Used by HtmlUtils::EscapeString().
    if (str.indexOf(ENT_aposnumeric) != -1) {
	str = str.replace(REENT_aposnumeric, CHAR_apos);
    }

    if (str.indexOf(ENT_apos) != -1) {
	str = str.replace(REENT_apos, CHAR_apos);
    }

    return xmlUnescapeText(str);
}


/**
 * Strips all the HTML tags in string. Converts rather than removes
 * space and newline.
 *
 * @param {string} str The string possibly containing HTML.
 * @return {string} The string with all HTML unescaped or removed.
 */
function stripHtmlTags(str) {
    return xmlUnescapeAttr(str.replace(/<!--.*?-->/g, "").
			   replace(/<br(\/?|\s[^>]*)>/ig, "\n").
			   replace(/<\/?\w[^>]*>/g, "").
			   replace(/&nbsp;/g, " "));
}

function OnionLayer() {
}

/**
 * Returns the extended content for the given feature. This in turn contains
 * different protos as filled in by the different onion backends, e.g.
 * maps_layers::BasicContent / maps_layers::GeowikiExtendedContent etc.
 *
 * @param {Object} feature Feature data.
 * @return {Object}
 */
OnionLayer.extendedFeatureContent = function(feature) {
    // The content field in the feature object as delivered by the PaintFE is
    // a JSON-string. We evaluate it lazily when needed.
    var content = feature[ONION_PROP_CONTENT];
    if (typeof content == 'string') {
	content = feature[ONION_PROP_CONTENT] = eval('[' + content + '][0]');
    }
    return content;
};

/**
 * Returns the basic content (maps_layers::BasicContent) from the given feature.
 * Null if this is not present.
 * @param {Object} feature Feature data.
 * @return {Object}
 */
OnionLayer.basicFeatureContent = function(feature) {
    var content = OnionLayer.extendedFeatureContent(feature);
    return (content && content[ONION_PROP_BASIC_CONTENT]) || null;
};


/**
 * Returns the *html-stripped and escaped* title for the given feature.
 * @param {Object} feature Feature data.
 * @return {string} Blank if no title found.
 */
OnionLayer.featureTitle = function(feature) {
    var featureContent = OnionLayer.basicFeatureContent(feature);
    var title = stripHtmlTags(
			      (featureContent && featureContent[ONION_PROP_TITLE]) || '');
    return title;
};


/**
 * @param {Object} feature Feature data.
 * @param {GProjection} projection The projection to be used.
 * @return {GLatLng} Latlng for the given feature. Null if no data found.
 */
OnionLayer.featureLatLng = function(base, feature, projection) {
    var anchor = feature[ONION_PROP_ANCHOR];
    // If anchor not found, e.g. for shapes and lines, use the cursor position.
    var markerLatLng = null;
    if (anchor) {
	var power = Math.pow(2, ONION_REFERENCE_ZOOM);
	var x = (anchor[0] + base[0]) / Math.pow(2, ONION_REFERENCE_ZOOM);
	var y = (anchor[1] + base[1]) / Math.pow(2, ONION_REFERENCE_ZOOM);
	var p = new google.maps.Point(x,  y);
	markerLatLng = projection.fromPointToLatLng(p);
    }
    return markerLatLng;
};
